const hx = require('hbuilderx')
const crypto = require('crypto')
const fs = require('fs-extra')
const { join } = require('path')
const os = require('os')
const forge = require('node-forge')

const descriptions = {
    RSA: `
1. RSA 是一种非对称加密算法，广泛用于安全数据传输和数字签名。
2. 它依赖于大素数的乘积的难解性，密钥长度通常为 2048 位或更长。
3. RSA 是最常用的公钥加密算法之一，适用于加密和签名验证。`,
    DSA: `
1. DSA是一种用于数字签名的非对称加密算法。
2. 它基于离散对数问题的难解性，通常用于确保数据的完整性和真实性。
3. DSA 的密钥长度通常为 2048 位，广泛应用于数字签名标准（DSS）。`,
    ECC: `
1. ECC 是一种基于椭圆曲线数学的非对称加密算法。
2. 它在较小的密钥长度下提供与 RSA 相同的安全性，通常用于资源受限的环境。
3. ECC 被广泛应用于现代安全协议，如 TLS 和数字货币。`,
    PEM: `
1. PEM 是一种常用的文件格式，用于存储和传输加密密钥和证书。
2. PEM 文件通常是 Base64 编码的文本文件。
3. PEM 格式广泛应用于 SSL/TLS 证书和密钥的存储。`,
    DER: `
1. DER 是一种二进制格式，用于存储和传输加密密钥和证书。
2. DER 以二进制形式存储数据。
3. DER 格式常用于证书和密钥。`,
    OpenSSH: `
1. OpenSSH 是一种常用的文件格式，通常用于 SSH 登录服务器。
2. OpenSSH 文件通常由 PEM 转换而来。
3. OpenSSH 常用于服务器登录。`
}

function generateKeyPair(opts) {
    return new Promise((resolve, reject) => {
        let name = ''
        let format = opts.format
        if (opts.format == 'OpenSSH') {
            format = 'pem'
        }
        const options = {
            publicKeyEncoding: {
                type: 'spki',
                format
            },
            privateKeyEncoding: {
                type: 'pkcs8',
                format
            }
        }
        if (opts.passphrase) {
            options.privateKeyEncoding.cipher = 'aes-256-cbc'
            options.privateKeyEncoding.passphrase = opts.passphrase
        }
        if (opts.algorithm === 'RSA') {
            name = 'rsa'
            options.modulusLength = parseInt(opts.keyLength)
        } else if (opts.algorithm === 'DSA') {
            name = 'dsa'
            options.modulusLength = parseInt(opts.keyLength)
        } else if (opts.algorithm === 'ECC') {
            name = 'ec'
            options.namedCurve = opts.curve
        } else {
            reject(new Error('不支持此算法类型'))
        }
        crypto.generateKeyPair(name, options, (error, publicKey, privateKey) => {
            if (error) return reject(error)
            if (opts.format == 'OpenSSH') {
                const pemPub = forge.pki.publicKeyFromPem(publicKey)
                publicKey = forge.ssh.publicKeyToOpenSSH(pemPub)
                const pem = forge.pki.privateKeyFromPem(privateKey)
                privateKey = forge.ssh.privateKeyToOpenSSH(pem)
            }
            resolve({
                publicKey,
                privateKey
            })
        })
    })
}

function createForm({ title, subtitle, form }) {
    return {
        title: title,
        subtitle: subtitle,
        formItems: [
            {
                type: 'widgetGroup',
                name: 'algorithmGroup',
                widgets: [
                    {
                        type: 'label',
                        name: 'algorithmLabel',
                        text: '密钥算法'
                    },
                    {
                        type: 'radioButton',
                        name: 'algorithm',
                        text: 'RSA',
                        checked: form.algorithm == 'RSA'
                    },
                    {
                        type: 'radioButton',
                        name: 'algorithm',
                        text: 'DSA',
                        checked: form.algorithm == 'DSA'
                    },
                    {
                        type: 'radioButton',
                        name: 'algorithm',
                        text: 'ECC',
                        checked: form.algorithm == 'ECC'
                    }
                ]
            },
            {
                type: 'label',
                name: 'keyLengthLabel',
                text: '密钥长度'
            },
            {
                type: 'comboBox',
                name: 'keyLength',
                editable: true,
                items: ['1024', '2048', '4096'],
                text: form.keyLength
            },
            {
                type: 'widgetGroup',
                name: 'formatGroup',
                widgets: [
                    {
                        type: 'label',
                        name: 'formatLabel',
                        text: '密钥格式'
                    },
                    {
                        type: 'radioButton',
                        name: 'format',
                        text: 'OpenSSH',
                        checked: form.format == 'OpenSSH'
                    },
                    {
                        type: 'radioButton',
                        name: 'format',
                        text: 'PEM',
                        checked: form.format == 'PEM'
                    },
                    {
                        type: 'radioButton',
                        name: 'format',
                        text: 'DER',
                        checked: form.format == 'DER'
                    }
                ]
            },
            {
                type: 'input',
                name: 'passphrase',
                label: '私钥密码',
                value: form.passphrase
            },
            {
                type: 'fileSelectInput',
                name: 'path',
                mode: 'folder',
                label: '保存位置',
                value: form.path
            }
        ]
    }
}

async function generate(form) {
    const name = {
        RSA: 'rsa',
        DSA: 'dsa',
        ECC: 'ec'
    }[form.algorithm]
    let publicKeyName
    let privateKeyName
    const opts = {}
    switch (form.format) {
        case 'PEM':
            publicKeyName = 'id_' + name + '.pub.pem'
            privateKeyName = 'id_' + name + '.pem'
            opts.encoding = 'utf-8'
            break
        case 'DER':
            publicKeyName = 'id_' + name + '.pub.der'
            privateKeyName = 'id_' + name + '.der'
            break
        case 'OpenSSH':
            publicKeyName = 'id_' + name + '.pub'
            privateKeyName = 'id_' + name
            opts.encoding = 'utf-8'
            break
    }
    const publicKeyPath = join(form.path, publicKeyName)
    if (await fs.pathExists(publicKeyPath)) {
        throw new Error('目标公钥已存在: ' + publicKeyPath)
    }
    const privateKeyPath = join(form.path, privateKeyName)
    if (await fs.pathExists(privateKeyPath)) {
        throw new Error('目标私钥已存在: ' + privateKeyPath)
    }
    const { publicKey, privateKey } = await generateKeyPair(form)
    fs.writeFile(publicKeyPath, publicKey, opts)
    fs.writeFile(privateKeyPath, privateKey, opts)
    return {
        publicKeyPath,
        privateKeyPath
    }
}

module.exports = async function () {
    const form = {
        algorithm: 'RSA',
        format: 'OpenSSH',
        path: join(os.homedir(), '.ssh'),
        passphrase: '',
        keyLength: '2048',
        curve: 'P-256'
    }
    let ui = createForm({
        title: '密钥对生成器',
        subtitle: '使用指定的算法, 生成非对称密钥对',
        form
    })
    let result
    await hx.window.showFormDialog({
        ...ui,
        width: 490,
        height: 500,
        submitButtonText: '生成',
        cancelButtonText: '取消',
        hideFooterSeparator: true,
        validate: async function () {
            const keyLength = parseInt(form.keyLength)
            if (isNaN(keyLength)) {
                this.showError('密钥长度必须是1024整数倍的数字')
                return false
            }
            if (keyLength % 1024 != 0) {
                this.showError('密钥长度必须是1024的整数倍')
                return false
            }
            if (!form.path) {
                this.showError('请选择密钥文件保存路径')
                return false
            }
            try {
                result = await generate(form)
            } catch (error) {
                this.showError(error.message)
                return false
            }
            return true
        },
        onChanged: function (field, value) {
            let prop = ''
            if (field == 'algorithmGroup') {
                form.algorithm = value.changedWidget.text
                prop = form.algorithm
            } else if (field == 'formatGroup') {
                form.format = value.changedWidget.text
                prop = form.format
            } else {
                form[field] = value
                prop = value
            }
            const desc = descriptions[prop]?.trim()
            this.updateForm({
                title: '生成密钥对',
                subtitle: desc
            })
        }
    })
    return result
}
